#include "Terrain.h"
#include "../camera/Camera.h"

#include "../helper/Helper.h"
#include "../stdafx.h"

#include "../Global.h"

#include "../shader/Shader.h"
#include "../shader/CgShader.h"
#include "../light/Light.h"
#include "../math/Math.h"

/* shader */
Shader* shader;
float scale = 8.0f;
float delta = 1.5f;
float down = -15.0f;

/* light */
Light sunLight;

/* terrain auto rotation */
float yAngle;

Terrain::Terrain() : heightmap(0), isInfinite(0), generatedTexture(0), generatedTextureWidth(0), generatedTextureHeight(0), isLightmapping(1), /*isWaterEnable(0),*/ isGroundEnable(1)
{
	/* Get camera instance */
	camera = Global::GetGlobal()->GetCamera();
	
	textures = new GLuint[TEXTURE_TERRAIN_COUNT];
	tile = new GLubyte*[TEXTURE_TERRAIN_COUNT];
	
	/* Allocate texture objects */
	AllocateTextures();
	
	/* Load height map */
	//LoadHeightMap("texture/heightmap/heightmap3.bmp");
	//LoadHeightMap("texture/heightmap/terrain6.bmp");
	LoadHeightMap("texture/heightmap/terrain10.bmp");
	//LoadHeightMap("texture/heightmap/terrain1.jpg");
	
	// load detail map
	LoadDetailMap("texture/heightmap/detailmap.bmp");
	//LoadDetailMap("texture/heightmap/default_detailmap.jpg");
	
	/* color */
	CalculateHeightColor();
	
	//Load texture
	CreateTileTextures();
	
	/* (!) Calculate lightmap after calculating tile textures since we make the size of lightmap the same as the size of the generated texture */
	CalculateLightmap();	
	CreateLightTextures();
	
	/* Finally, load the generated texture */
	LoadTileTextures();
	LoadLightTextures();
	
	/* Load vertice based on height map */
	LoadVertice();
	
	/* Water */
	//water = new Water;
	
	/* Shader */
	shader = new CgShader;
	shader->LoadVertex("shader/phong.vp", "matViewProj", "matTransform", "#");
	//shader->LoadVertex("shader/simple_vertex.vp");
	//shader->LoadVertex("shader/green.vp");
	shader->LoadPixel("shader/phong.fp", /* param */
						"@texture",
						"@detailTexture",
						"eyePosition", 
						"lightPosition", 			
						"lightColor", 
						"materialAmbient", 
						"materialDiffuse", 
						"materialSpecular", 
						"shininess", 
						"materialColor",
						"#" //end sign
						);
		
	/* light */
	sunLight.pos = Vector3f(0, 550, 0);
	sunLight.ambient = Vector3f(1, 1, 1);
	sunLight.diffuse = Vector3f(1, 1, 1);
	sunLight.specular = Vector3f(1, 1, 1);
	
	/* terrain material */
	this->material = Material(0.5, 0.9, 0.7, 11);
	this->material.color = Vector3f(1.0f, 1.0f, 1.0f);
	this->material.reflection = 0.0f;
}

Terrain::~Terrain()
{
	ReleaseNewArray(textures);
	ReleaseNewArray(heightmap);
	ReleaseNew(surface);
	ReleaseNewArray(generatedTexture);
	ReleaseNewArray(lightmap);
	//ReleaseNew(water);	
	ReleaseNewArray(vertice);
	ReleaseNew(shader);
}

void Terrain::LoadHeightMap(char* heightmapFile) {
	surface = Helper::LoadTextureImage(heightmapFile);	
	printf("IMG_Load: %s\n", IMG_GetError());
	
	//save terrain size
	width = surface->w;
	height = surface->h;	
	GLubyte* data = (GLubyte*)surface->pixels;
	//save heightmap	
	heightmap = new GLubyte[width*height];
	
	int padWidth = Helper::PadBytes(width);
	
	GLubyte* pHeight = heightmap;
	GLubyte* pixels = (GLubyte*)surface->pixels;
	
	switch(surface->format->BitsPerPixel) {
	case 8:	
		for (int i = 0; i < height; i++) {//scan rows
			memcpy(pHeight, pixels, width);
			//jump over padded distance
			pHeight += width;
			pixels += padWidth;
		}		
		break;
	case 24:
		for (int i = 0; i < width*height; i++)
			heightmap[i] = data[i*3];
		break;
	default:
		printf("Not support image depth for heightmap.\n");
		exit(1);
		break;
	}	
	//memset(heightmap, 0, width*height);
}

void Terrain::LoadVertice() {
	int width2 = width / 2;
	int height2 = height / 2;
	int h = 0;
	
	/* n is the number of vertices along each size of the terrain
	   This triangle strip uses n * (n + n - 2) or (n - 1) * n * 2 approximates to 2*n*n vertices
	*/
	
	numVertice = width*height;
	vertice = new Vertex[numVertice];
	
	Vertex* v = vertice;
	int z = 0, x = 0;
	for (z = 0; z < height; z++) {
		for (x = 0; x < width; x++) {
			float coordX = (x - width2) * scale;					
			float coordZ = (z - height2) * scale; //from back to front as the orientation of the heightmap image	
			
			h = heightmap[z*width+x];  GetColor(h);  
			v->position.Assign(coordX, h * delta + down, coordZ);
			//v->normal.Assign(0, 1, 0);
			v->texture.u = (float)x / (width-1);
			v->texture.v = (float)z / (height-1);
			v++;			
		}
	}
	
	/* normal computation */
	Vertex* pHere = vertice, *pRight = pHere+1, *pBottom = pHere+width, *pDiagonal = pBottom+1;
	Vector3f v1;
	Vector3f v2;
	Vector3f cross;
	for (z = 0; z < height - 1; z++) {
		for (x = 0; x < width - 1; x++) {
			/* first triangle */
			v1 = pBottom->position - pHere->position;
			v2 = pRight->position - pHere->position;
			cross = v1.Cross(v2);
			pHere->normal += cross;
			pRight->normal += cross;
			pBottom->normal += cross;
			pHere->tag++;
			pRight->tag++;
			pBottom->tag++;
			
			/* second triangle */
			v1 = pRight->position - pDiagonal->position;
			v2 = pBottom->position - pDiagonal->position;			
			cross = v1.Cross(v2);
			pDiagonal->normal += cross;
			pRight->normal += cross;
			pBottom->normal += cross;
			pDiagonal->tag++;
			pRight->tag++;
			pBottom->tag++;			
			
			//next column
			pHere++;
			pRight++;
			pBottom++;
			pDiagonal++;
		}
		
		//jump to next row
		pHere++;
		pRight++;
		pBottom++;
		pDiagonal++;
	}
	
	pHere = vertice;
	for (z = 0; z < height; z++) {
		for (x = 0; x < width; x++, pHere++) {
			pHere->normal /= pHere->tag;
			pHere->tag = 0;
		}
	}
}

GLvoid* Terrain::GetPixels() {	
	return surface->pixels;
}
void Terrain::GetTrueSize(int& trueWidth, int& trueHeight) {
	//256 pixel of height map -> 255 quads per row
	trueWidth = width-1;
	trueHeight = height-1;
}

/*void makeColor(int height) {	
	if (height < 0) return;
	else if (height < 50) 
		glColor3f(0, 0, 1); //sea
	else if (height < 100)
		glColor3f(0, 1, 0); //hill
	else if (height < 150)
		glColor3f(0, 0.75, 0); //mountain
	else if (height < 200)
		glColor3f(0.75, 0.75, 0.75);
	else
		glColor3f(1, 1, 1);
}*/

void Terrain::GetColor(int height) {
	Color3f color = heightColor[height];
	//glColor3f(color.red, color.green, color.blue);
}



Color3f interpolateColor(int height) {
	Region water(0, 50, COLOR_F(67), COLOR_F(112), COLOR_F(121)); //inclusive
	Region sand(51, 80, COLOR_F(177), COLOR_F(143), COLOR_F(58));
	Region grass(81, 180, COLOR_F(129), COLOR_F(177), COLOR_F(58));
	Region rock(181, 234, COLOR_F(179), COLOR_F(165), COLOR_F(130));
	Region snow(235, 255, 1.0f, 1.0f, 1.0f);
	Region region[] = {water, sand, grass, rock, snow};
	int regionCount = 5;
	
	float r = 0.0f, g = 0.0f, b = 0.0f;
	float w = 0.0f;
	/* C programming style */	
	/*for (int i = 0; i < regionCount; i++) {
		w = (height - region[i].min) / (region[i].max - region[i].min);
		//w = (height - region[i].min) / 51;
		//w = 1 - abs(height - region[i].max) / 60;
		if (w > 1) w = 0;
		if (w < 0) w = 0;
		
		r += w * region[i].r;
		g += w * region[i].g;
		b += w * region[i].b;
	}*/
	
	/* My method */	
	
	float range = 10.0f;
	for (int i = 0; i < regionCount; i++) {
		if (height < region[i].min)
			w = 1 - (region[i].min - height) / range;
		else if (height > region[i].max)
			w = 1 - (height - region[i].max) / range;
		else 
			w = 1;
		if (w < 0) w = 0;
		
		r += w * region[i].r;
		g += w * region[i].g;
		b += w * region[i].b;			
	}
	
	
	
	
	return Color3f(r, g, b);
}

Color3i Terrain::InterpolateTile(int height, int index) {
	/*
	Region water(0, 30, COLOR_F(67), COLOR_F(112), COLOR_F(121)); //inclusive
	Region sand(40, 50, COLOR_F(177), COLOR_F(143), COLOR_F(58));
	Region grass(70, 150, COLOR_F(129), COLOR_F(177), COLOR_F(58));
	Region rock(160, 200, COLOR_F(179), COLOR_F(165), COLOR_F(130));
	Region snow(210, 255, 1.0f, 1.0f, 1.0f);
	*/
	Region water(0, 40, COLOR_F(67), COLOR_F(112), COLOR_F(121)); //inclusive
	Region sand(40, 100, COLOR_F(177), COLOR_F(143), COLOR_F(58));
	Region grass(100, 180, COLOR_F(129), COLOR_F(177), COLOR_F(58));
	Region rock(180, 230, COLOR_F(179), COLOR_F(165), COLOR_F(130));
	Region snow(230, 255, 1.0f, 1.0f, 1.0f);
	Region region[] = {water, sand, grass, rock, snow};
	int regionCount = 5;
	
	float r = 0, g = 0, b = 0;
	float w = 0.0f;	
		
	/*for (int i = 0; i < regionCount; i++) {
		if (height >= region[i].min && height <= region[i].max) {		
			r = (int)(tile[i][index]);
			g = (int)(tile[i][index+1]);
			b = (int)(tile[i][index+2]);
		}
	}*/
	
	
	/* My method */
	/*
	float range;
	for (int i = 0; i < regionCount; i++) {
		if (height < region[i].min) {
			range = region[i].min - region[i-1].max - 2;
			if (range <= 0) w = 0;
			else 
				w = (region[i].min - height - 1) / range;
		}
		else if (height > region[i].max) {
			range = region[i+1].min - region[i].max - 2;
			if (range <= 0) w = 0;
			else
				w = (height - region[i].max - 1) / range;
		}
		else 
			w = 1;
		//printf("%.2f\n", w);
		if (w > 1) w = 0;
		if (w < 0) w = 0;
		
		r += (w * tile[i][index]);
		g += (w * tile[i][index+1]);
		b += (w * tile[i][index+2]);
		
		
		if (r > 255) 
				printf("bug red\n");
		if (g > 255) 
			printf("bug green\n");
		if (b > 255) 
			printf("bug blue\n");
	}*/
	
	int m, M;
	int delta = 16; // note: m + delta must be < M - delta. Should not have any intersection.
	int delta2 = delta * 2;
	
	for (int i = 0; i < regionCount; i++) {
		m = region[i].min;
		M = region[i].max;
		// compute weight factor for each region
		if (height < m - delta) {
			w = 0;
		} else if (height < m + delta) {
			w = (height - (m - delta)) * 1.0f / delta2;
		} else if (height < M - delta) {
			w = 1;
		} else if (height < M + delta) {
			w = ((M + delta) - height) * 1.0f / delta2;
		} else {
			w = 0;
		}
		
		r += (w * tile[i][index]);
		g += (w * tile[i][index+1]);
		b += (w * tile[i][index+2]);	
	}
	if (r > 255) r = 255;
	if (g > 255) g = 255;
	if (b > 255) b = 255;
	
	return Color3i(r, g, b);
}

void Terrain::CalculateHeightColor() {
	for (int h = 0; h < HEIGHT_COUNT; h++) {//every 
		heightColor[h] = interpolateColor(h);
	}
}



/* Get corresponding height from the height map */
int Terrain::GetHeight(Vector3f curPos, float &result) {	
	Vector3f pos;
	if (this->IsInfinite())
		pos = TestBoundary(curPos); //if infinite then boundary
	else 
		pos = curPos;
	
	int x = pos.x / scale + width / 2;
	int z = height / 2 - pos.z / scale;	
	if (x >= 0 && x < width && z >= 0 && z < height) {
		int index = z*width+x;
		result = heightmap[index] * delta + down;
		return 1;
	}
	else return 0; //Out of bound	
}

void Terrain::GetSize(float &fWidth, float &fHeight) {
	//256 pixels of height map -> 255 quads per line
	fWidth = (width-1) * scale;
	fHeight = (height-1) * scale;
}

void Terrain::RenderTerrain() {
	if (isGroundEnable) {
		//glColor3f(1.0f, 1.0f, 1.0f);
		BeginAlignPosition(); //translate + rotate
		
		float extent = 30.0f;
		float step = 0.1f;
		
		glPushAttrib(GL_ENABLE_BIT);
			
		/* TRIANGLE_STRIPS */
		if (heightmap == NULL) {
			glDisable(GL_TEXTURE_2D); //disable texture
			
			float y = -5.0f;
			
			for (float x = -extent; x < extent; x += step) {
				for (float z = extent; z > -extent; z -= step) {
					glBegin(GL_TRIANGLE_STRIP);			
						glVertex3f(x, y, z);
						glVertex3f(x + step, y, z);
						glVertex3f(x, y, z - step);
						glVertex3f(x + step, y, z - step);
					glEnd();
				}
			}
		} else { //use heightmap
			glEnable(GL_DEPTH_TEST);
			//glColor3f(1.0f, 1.0f, 1.0f);
			//if (isLightmapping)
			//	glBindTexture(GL_TEXTURE_2D, textures[1]);
			//else
				//glBindTexture(GL_TEXTURE_2D, textures[0]);
			//glEnable(GL_TEXTURE_2D); //enable texture 2d
						
			Vertex* p1 = vertice; //first row
			Vertex* p2 = p1 + width; //second row
			for (int z = 0; z < height - 1; z++) {
				glBegin(GL_TRIANGLE_STRIP); //start of a row of triangle strip
				for (int x = 0; x < width; x++) {
					//printf("%.2f %.2f %.2f\n", p1->normal.x, p1->normal.y, p1->normal.z);
					if (isLightmapping)
						glNormal3f(p1->normal.x, p1->normal.y, p1->normal.z);
					else
						glNormal3f(0, 0, 0);
					glTexCoord2f(p1->texture.u, p1->texture.v);
					glVertex3f(p1->position.x, p1->position.y, p1->position.z);
					p1++;
					
					if (isLightmapping)
						glNormal3f(p2->normal.x, p2->normal.y, p2->normal.z);
					else
						glNormal3f(0, 0, 0);
					glTexCoord2f(p2->texture.u, p2->texture.v);
					glVertex3f(p2->position.x, p2->position.y, p2->position.z);
					p2++;
				}
				glEnd();
			}	
			
						
			/* Ideal method should use n*n vertices together with an index buffer */
			//TODO: Improve performance with glVertexArray or VBO (vertex buffer object)
		}
		glPopAttrib();	
		
		EndAlignPosition();
	}	
}

GLubyte bitmap[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
void Terrain::RenderHeightmap() {
	//TODO: Use camera->Begin2dMode via Global singleton
	Vector3f eye = camera->GetEye();
	camera->Begin2dMode();
		
	//Draw, remember to disable textures
	glPushAttrib(GL_ENABLE_BIT); //disable texture
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);
				
		float fWidth, fHeight;
		GetSize(fWidth, fHeight);
		float fWidth2 = fWidth / 2;
		float fHeight2 = fHeight / 2;
		
		//Draw the eye point also		
		int x = ((eye.x + fWidth2) / fWidth) * width;
		int z = ((eye.z + fHeight2) / fHeight) * height;
		if (x < 0) x = 0;
		if (x > width) x = width;
		if (z < 0) z = 0;
		if (z > height) z = height;
				
		z = camera->GetWindowHeight() - z;
		//printf("%.2f %.2f %d %d\n", eye.x, eye.z, x, z);		 
		glRasterPos2i(0, camera->GetWindowHeight());				
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);	
		glPixelZoom(1, -1);		
		glDrawPixels(width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, heightmap);
		
		glColor3f(1.0f, 1.0f, 1.0f);
		glRasterPos2i(x, z);		
		glPixelZoom(10, 10); //no effects???
		glBitmap(3, 3, 0, 0, 0, 0, bitmap);
	glPopAttrib();
	
	//End 2d mode
	camera->End2dMode();
}

void Terrain::RenderLightmap() {
	Vector3f eye = camera->GetEye();
	camera->Begin2dMode();
		
	//Draw, remember to disable textures
	glPushAttrib(GL_ENABLE_BIT); //disable texture
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);
		 
		glRasterPos2i(width + generatedTextureWidth, camera->GetWindowHeight());				
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);	
		glPixelZoom(1, -1);		
		glDrawPixels(generatedTextureWidth, generatedTextureHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, lightmap);
	glPopAttrib();
	
	camera->End2dMode();
}

void Terrain::RenderTileTexture() {
	Vector3f eye = camera->GetEye();	
	camera->Begin2dMode();	
		
	//Draw, remember to disable textures
	glPushAttrib(GL_ENABLE_BIT); //disable texture
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);
				
		float fWidth, fHeight;
		GetSize(fWidth, fHeight);
		float fWidth2 = fWidth / 2;
		float fHeight2 = fHeight / 2;
		
		//Draw the eye point also		
		int x = ((eye.x + fWidth2) / fWidth) * width;
		int z = ((eye.z + fHeight2) / fHeight) * height;
		if (x < 0) x = 0;
		if (x > width) x = width;
		if (z < 0) z = 0;
		if (z > height) z = height;
				
		z = camera->GetWindowHeight() - z;
		x += width;
		//printf("%.2f %.2f %d %d\n", eye.x, eye.z, x, z);
		
		glRasterPos2i(width, camera->GetWindowHeight());				
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);	
		glPixelZoom(1, -1);		
		glDrawPixels(generatedTextureWidth, generatedTextureHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, generatedTexture);
		
		glColor3f(1.0f, 0.0f, 0.0f);
		glRasterPos2i(x, z);		
		glPixelZoom(10, 10); //no effects???
		glBitmap(3, 3, 0, 0, 0, 0, bitmap);
	glPopAttrib();
	
	camera->End2dMode();
}

void Terrain::RenderLightTexture() {
	Vector3f eye = camera->GetEye();
	camera->Begin2dMode();
		
	//Draw, remember to disable textures
	glPushAttrib(GL_ENABLE_BIT); //disable texture
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);
				
		float fWidth, fHeight;
		GetSize(fWidth, fHeight);
		float fWidth2 = fWidth / 2;
		float fHeight2 = fHeight / 2;
		
		//Draw the eye point also		
		int x = ((eye.x + fWidth2) / fWidth) * width;
		int z = ((eye.z + fHeight2) / fHeight) * height;
		if (x < 0) x = 0;
		if (x > width) x = width;
		if (z < 0) z = 0;
		if (z > height) z = height;
				
		z = camera->GetWindowHeight() - z;
		x += width;
		//printf("%.2f %.2f %d %d\n", eye.x, eye.z, x, z);		
		 
		glRasterPos2i(width, camera->GetWindowHeight());				
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);	
		glPixelZoom(1, -1);		
		glDrawPixels(generatedTextureWidth, generatedTextureHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, lightTexture);
		
		glColor3f(1.0f, 0.0f, 0.0f);
		glRasterPos2i(x, z);		
		glPixelZoom(10, 10); //no effects???
		glBitmap(3, 3, 0, 0, 0, 0, bitmap);
	glPopAttrib();
	
	camera->End2dMode();
}

void Terrain::RenderInfinite() {
	float fWidth, fHeight;
	this->GetSize(fWidth, fHeight);
	Vector3f oldPos = pos;
	//from eye point in a terrain, draw 8 terrains around it	
	int i = 0, j = 0;
	for (i = -1; i <= 1; i++) 
	{
		for (j = -1; j <= 1; j++) 
		{
			this->pos = oldPos + Vector3f(fWidth * j, 0, fHeight * i);
			this->RenderTerrain();
		}
	}
	
	//restore
	pos = oldPos;
}

void Terrain::Render() {	
//	camera->BeginIdentityMode();
//	glMatrixMode(GL_MODELVIEW_MATRIX);
//	glLoadMatrixf(matViewProj);
	glPushMatrix();
	float matRotation[16];
	
	if (isLightmapping) { //phong shading, must transform position and normal
		Math::RotateY(yAngle*M_PI/180, matRotation);
	} else { //keep normal = (0, 0, 0) and no transformation is made so that normal is always (0, 0, 0)
		Math::MatrixIdentity(matRotation);
	}
	float* matViewProj = camera->GetViewProjectionMatrix();
	shader->Begin();		
		shader->SetMatrixParamfc("matViewProj", matViewProj);
		shader->SetMatrixParamfc("matTransform", matRotation);
		//if (IsLightmapping())
		//	shader->SetTextureParam("texture", textures[1]);
		//else
			shader->SetTextureParam("texture", textures[0]);
			shader->SetTextureParam("detailTexture", textures[2]);
		//printf("Begin set some param\n");
		float f[3];
		//eye position
		camera->GetEye().ToFloat3(f);
		shader->SetParamPixel3fv("eyePosition", f);
		
		//light position
		sunLight.pos.ToFloat3(f);
		// make eye position as light source
		//camera->GetEye().ToFloat3(f);
		//Math::TransformVector(matViewProj, f, f);		
		shader->SetParamPixel3fv("lightPosition", f);
				
		//light color, take diffuse as its main color
		sunLight.diffuse.ToFloat3(f);
		shader->SetParamPixel3fv("lightColor", f);
		
		//material
		material.color.ToFloat3(f);
		shader->SetParamPixel3fv("materialColor", f);
		shader->SetParamPixel1fv("materialAmbient", material.ambient);
		shader->SetParamPixel1fv("materialDiffuse", material.diffuse);
		shader->SetParamPixel1fv("materialSpecular", material.specular);
		shader->SetParamPixel1dv("shininess", material.phongShininess);
		shader->Update();
		//printf("Set param successfully\n");
		
		glColor3f(1.0f, 1.0f, 1.0f);
		
		if (isInfinite == 0) { //normal mode 
			this->RenderTerrain();
		} else { //infinite mode
			this->RenderInfinite();
		}
			
		/*
		glColor3f(1.0f, 1.0f, 1.0f);
		glBegin(GL_QUADS);
			glVertex3f(0.5, 0, 0);
			glVertex3f(0.5, 0.5, 0);
			glVertex3f(0, 0.5, 0);
			glVertex3f(0, 0, 0);
		glEnd();
		
		glBegin(GL_TRIANGLES);
		    glVertex2f(-0.8, 0.8);
		    glVertex2f(0.8, 0.8);
		    glVertex2f(0.0, -0.8);
		  glEnd();*/
		  
	shader->End();
	

	
	//camera->EndIdentityMode();
	/* Render water */
	/*if (isWaterEnable) {
		RenderWater();
	}*/
	
	glPopMatrix();
	
	//RenderHeightmap();
	//RenderTileTexture();
	/* render perlin noise */
	//water->RenderPerlinNoise();
}

int Terrain::IsInfinite() {return isInfinite;}
void Terrain::BeginInfinite() {isInfinite = 1;}
void Terrain::EndInfinite() {isInfinite = 0;}

//Test if a point is inside the boundary of the center region 
Vector3f Terrain::TestBoundary(Vector3f p) {
	float fWidth, fHeight;
	//get geometry size of the terrain region
	GetSize(fWidth, fHeight);
	float fWidth2 = fWidth / 2;
	float fHeight2 = fHeight / 2;
	
	if (p.x < pos.x - fWidth2) p.x += fWidth;
	else if (p.x > pos.x + fWidth2) p.x -= fWidth;
	
	if (p.z < pos.z - fHeight2) p.z += fHeight;
	else if (p.z > pos.z + fHeight2) p.z -= fHeight;
	return p;
}

void Terrain::CreateTileTextures() {	
	/* Generate texture */
	SDL_Surface* water = Helper::LoadTextureImage("texture/tile/sand2.bmp");
	SDL_Surface* sand = Helper::LoadTextureImage("texture/tile/grass5.bmp");
	SDL_Surface* grass = Helper::LoadTextureImage("texture/tile/grass5.bmp");
	SDL_Surface* rock = Helper::LoadTextureImage("texture/tile/grass5.bmp");
	SDL_Surface* snow = Helper::LoadTextureImage("texture/tile/grass.bmp");
	//SDL_Surface* rock = Helper::LoadTextureImage("texture/tile/rock.bmp");
	//SDL_Surface* snow = Helper::LoadTextureImage("texture/tile/snow.bmp");
	
	//Init tile for interpolation
	int tileCount = 5;	
	GLubyte* tiles[] = {(GLubyte*)water->pixels, (GLubyte*)sand->pixels, (GLubyte*)grass->pixels, (GLubyte*)rock->pixels, (GLubyte*)snow->pixels};
	for (int i = 0; i < tileCount; i++) {
		this->tile[i] = tiles[i];
	}
	
	GLint tileWidth = water->w;
	GLint tileHeight = water->h;
	GLint iWidth = width; //must be the same width as heightmap
	GLint iHeight = height; //must be the same height as heightmap	
	
	/* Allocate memory for the new combined texture */
	/*generatedTexture = new GLubyte[iWidth*iHeight*3];
	generatedTextureWidth = iWidth;
	generatedTextureHeight = iHeight;*/
	generatedTexture = new GLubyte[tileWidth*tileHeight*3];
	generatedTextureWidth = tileWidth;
	generatedTextureHeight = tileHeight;
		
	int height;
	Color3i color; //use RGB in (0..255)
	int index;
	int tileIndex;
	for (int z = 0; z < iHeight; z++) {
		for (int x = 0; x < iWidth; x++) {			
			index = z*iWidth+x;
			height = heightmap[index];
			
			tileIndex = (z % tileHeight) * tileWidth + (x % tileWidth);			
			color = InterpolateTile(height, tileIndex*3);
			
			//block of 3 bytes R G B
			//generatedTexture[index*3] = color.red;
			//generatedTexture[index*3+1] = color.green;
			//generatedTexture[index*3+2] = color.blue;
			generatedTexture[tileIndex*3] = color.red;
			generatedTexture[tileIndex*3+1] = color.green;
			generatedTexture[tileIndex*3+2] = color.blue;
		}
	}	
	
	//cleanup
	ReleaseNew(water);
	ReleaseNew(sand);
	ReleaseNew(grass);
	ReleaseNew(rock);
	ReleaseNew(snow);	
}

void Terrain::AllocateTextures() {
	glGenTextures(TEXTURE_TERRAIN_COUNT, textures);
}

/* Load generated texture */
void Terrain::LoadTileTextures() {
	GLenum eFormat = GL_BGR_EXT; //swap for windows bitmap
	/* Texture loading and binding */			
	glBindTexture(GL_TEXTURE_2D, textures[0]);
	glTexImage2D(GL_TEXTURE_2D, 0 /* mipmap */, GL_RGB /* internal image format */, generatedTextureWidth, generatedTextureHeight, 0, eFormat, GL_UNSIGNED_BYTE, generatedTexture);
	//glTexImage2D(GL_TEXTURE_2D, 0 /* mipmap */, GL_RGB /* internal image format */, tileWidth, tileHeight, 0, eFormat, GL_UNSIGNED_BYTE, grass->pixels);
		
	//Texture setup
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

/* Load generated texture */
void Terrain::LoadLightTextures() {
	GLenum eFormat = GL_BGR_EXT; //swap for windows bitmap
	/* Texture loading and binding */			
	glBindTexture(GL_TEXTURE_2D, textures[1]);
	glTexImage2D(GL_TEXTURE_2D, 0 /* mipmap */, GL_RGB /* internal image format */, generatedTextureWidth, generatedTextureHeight, 0, eFormat, GL_UNSIGNED_BYTE, lightTexture);
	//glTexImage2D(GL_TEXTURE_2D, 0 /* mipmap */, GL_RGB /* internal image format */, tileWidth, tileHeight, 0, eFormat, GL_UNSIGNED_BYTE, grass->pixels);
		
	//Texture setup
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

void Terrain::CalculateLightmap() {
	/* allocate lightmap */
	lightTexture = new GLubyte[generatedTextureWidth*generatedTextureHeight*3];
	lightmap = new GLubyte[generatedTextureWidth*generatedTextureHeight];
	int index, preIndex;
	int curHeight, preHeight;
	int lightX = 10;
	int lightZ = 10;
	int z, x, preZ, preX;
	int deltaHeight;
	float softness = 180.0f;
	float shade;	
	int lightIndex;
	
	for (z = 0; z < height; z++) {
		for (x = 0; x < width; x++) {
			preZ = z - lightZ;
			preX = x - lightX;
			
			/* calculate shade factor */
			if (preX < 0) preX = 0;
			if (preZ < 0) preZ = 0;
			//if (preX < 0 || preZ < 0) shade = 0.5f;			
			//else {
				preIndex = preZ*width + preX;
				index = z*width + x;
				
				curHeight = heightmap[index];//257
				preHeight = heightmap[preIndex];
				
				deltaHeight = preHeight - curHeight;
				shade = 1.0f - (float)deltaHeight / softness;
				if (shade < 0) shade = 0;
				if (shade > 1) shade = 1;
			//}
			
			/* save into lightmap */
			lightIndex = (z % generatedTextureHeight) * generatedTextureWidth + (x % generatedTextureWidth);
			lightmap[lightIndex] = (GLubyte)(255 * shade);
		}
	}
}

void Terrain::CreateLightTextures() {	
	int z, x;
	int index;
	float shade;
	for (z = 0; z < generatedTextureHeight; z++) {
		for (x = 0; x < generatedTextureWidth; x++) {
			index = z*generatedTextureWidth+x;
			shade = (float)lightmap[index] / 255; 
			lightTexture[index*3] = generatedTexture[index*3]* shade;
			lightTexture[index*3 + 1] = generatedTexture[index*3 + 1] * shade;
			lightTexture[index*3 + 2] = generatedTexture[index*3 + 2] * shade;
		}
	}
}

void Terrain::BeginLightmapping() {
	isLightmapping = 1;
}

void Terrain::EndLightmapping() {
	isLightmapping = 0;
}

int Terrain::IsLightmapping() {
	return isLightmapping;
}

void Terrain::ToggleLightmapping() {
	isLightmapping = 1 - isLightmapping;
}

/* Water =================== */
/*
void Terrain::RenderWater() {
	//move "water" to the same position as terrain
	Vector3f oldPos = water->pos;
	water->pos = this->pos; //change pos
	water->Render();
	water->pos = oldPos; //restore
}

void Terrain::EnableWater() { 
	isWaterEnable = 1;
}

void Terrain::DisableWater() {
	isWaterEnable = 0;
}

int Terrain::IsWaterEnable() {
	return isWaterEnable;
}*/
/* ========================= */

void Terrain::Update(long time) {
	/*if (isWaterEnable) {
		water->Update(time);
	}*/
	
	/* attach light position to eye position */
	//sunLight.pos.Assign(camera->GetEye());	
	
	/* auto rotation */
	//yAngle += 0.03f * time;
	if (yAngle >= 360) yAngle -= 360;
}

void Terrain::EnableGround() {
	isGroundEnable = 1;
}

void Terrain::DisableGround() {
	isGroundEnable = 0;
}

int Terrain::IsGroundEnable() {
	return isGroundEnable;
}

void Terrain::LoadDetailMap(const char* file) {
	SDL_Surface* surface = Helper::LoadTextureImage(file);
	GLenum eFormat = GL_BGR_EXT; //swap for windows bitmap
	// texture loading and binding		
	glBindTexture(GL_TEXTURE_2D, textures[2]);
	glTexImage2D(GL_TEXTURE_2D, 0 /* mipmap */, GL_RGB /* internal image format */, surface->w, surface->h, 0, eFormat, GL_UNSIGNED_BYTE, surface->pixels);
		
	//Texture setup
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	
	delete surface;
	surface = 0;
	
	// enable detail map
	EnableDetailMap();
}

void Terrain::EnableDetailMap(int enable) {
	isDetailMap = enable;
}
